/**
 * common-util
 *
 * @author lihui
 */

// note isOn 判断字符串是不是 on 开头，并且 on 后首字母不是小写字母
const onRE = /^on[^a-z]/
const isOn = key => onRE.test(key)

// note remove 移除数组的第一项（直接改变原数组）
const remove = (arr, el) => {
  let index = arr.indexOf(el)
  if (index >= -1) arr.splice(index, 1)
}

// todo hasOwn 是不是自己本身所拥有的属性
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (val, key) => hasOwnProperty.call(val, key)

// todo objectToString 对象转字符串
const objectToString = Object.prototype.toString
const toTypeString = value => objectToString.call(value)

const isString = val => typeof val === 'string'

// todo toRawType（原始类型） 对象转字符串 截取后几位
const toRawType = val => toTypeString(val).slice(8, -1)

// todo isIntegerKey 判断是不是数字型的字符串key值
const isIntegerKey = key => isString(key) && key !== 'NaN' && key[0] !== '-' && '' + parseInt(key, 10) === key

// 例子:
isIntegerKey('a') // false
isIntegerKey('0') // true
isIntegerKey('011') // false
isIntegerKey('11') // true

const cacheStringFunction = fn => {
  const cache = Object.create(null)
  return str => {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }
}
const camelizeRE = /-(\w)/g
// todo 命名语法转换：行线命名 转 驼峰命名 abc-hello => abcHello
const camelize = cacheStringFunction(str => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})
camelize('abc-hello') //=> 'abcHello'

// todo 命名语法转换：驼峰命名 转 行线命名：onClick => on-click
const hyphenateRE = /\B([A-Z])/g
const hyphenate = cacheStringFunction(str => str.replace(hyphenateRE, '-$1').toLowerCase())
hyphenate('onClick') //=> 'on-click'

//  todo  capitalize 首字母大写：abc => Abc
const capitalize = cacheStringFunction(str => str.charAt(0).toUpperCase() + str.slice(1))
capitalize('abc') //=> 'Abc'

// todo toHandlerKey ：click => onClick
const toHandlerKey = cacheStringFunction(str => (str ? `on${capitalize(str)}` : ``))
toHandlerKey('click') //=> 'onClick'

// todo hasChanged 判断是改变
// todo Object.is 方法判断两个值是否为同一个值
const hasChanged1 = (value, oldValue) => !Object.is(value, oldValue)
const hasChanged2 = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue)

hasChanged1(NaN, NaN) //=> false
hasChanged2(NaN, NaN) //=> false
hasChanged1(1, 1) //=> false
hasChanged2(1, 1) //=> false
hasChanged1(+10, -10) //=> true
hasChanged2(+10, -10) //=> true

// todo invokeArrayFns 执行数组里的函数
const invokeArrayFns = (fns, arg) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}

// 例子：
const arr = [
  val => console.log(val + '的博客地址是：https://baidu.com'),
  val => console.log('百度搜索 百度 可以找到' + val),
  val => console.log('微信搜索 百度 可以找到关注' + val),
]
// invokeArrayFns(arr, '我')

// todo def 定义对象属性（属性不可重写）
const def = (obj, key, value) => {
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: false,
    value,
  })
}

// 例子
let obj = {}
def(obj, 'age', 2020)
obj.sex = 'male'
obj.age = 0 // 不可修改
obj.sex = 'female' // 可修改
// console.log(obj) //=> {sex:'female',age:2020}
// console.log(Object.getOwnPropertyDescriptors(obj))

// todo toNumber 转数字
const toNumber = val => {
  const n = parseFloat(val)
  return isNaN(n) ? val : n
}

toNumber('111') // 111
toNumber('a111') // 'a111'
parseFloat('a111') // NaN
isNaN(NaN) // true

/**
 * todo getGlobalThis 获取全局 this 指向
 * 获取全局 this 指向。
 * 初次执行肯定是 _globalThis 是 undefined。所以会执行后面的赋值语句。
 * 如果存在 globalThis 就用 globalThis。MDN globalThis
 * 如果存在self，就用self。在 Web Worker 中不能访问到 window 对象，但是我们却能通过 self 访
 * 到 Worker 环境中的全局对象。
 * 如果存在window，就用window。
 * 如果存在global，就用global。Node环境下，使用global。
 * 如果都不存在，使用空对象。可能是微信小程序环境下。
 * 下次执行就直接返回 _globalThis，不需要第二次继续判断了。这种写法值得我们学习。
 */
let _globalThis
const getGlobalThis = () => {
  return (
    _globalThis ||
    (_globalThis =
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
        ? self
        : typeof window !== 'undefined'
        ? window
        : typeof global !== 'undefined'
        ? global
        : {})
  )
}

// todo makeMap 传入一个以逗号分隔的字符串，生成一个 map(键值对)，并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项，输入的字符不区分大小写，全部会转换成小写。
function makeMap(str, expectsLowerCase) {
  const map = Object.create(null)
  const list = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}
// todo 案例
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
const HTML_TAGS =
  'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +
  'header,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +
  'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +
  'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +
  'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +
  'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +
  'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +
  'option,output,progress,select,textarea,details,dialog,menu,' +
  'summary,template,blockquote,iframe,tfoot'
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element
const SVG_TAGS =
  'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' +
  'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' +
  'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' +
  'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' +
  'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' +
  'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' +
  'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' +
  'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' +
  'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' +
  'text,textPath,title,tspan,unknown,use,view'
const VOID_TAGS = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'
const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS)
const isHTMLTag2 = /*#__PURE__*/ makeMap(HTML_TAGS, true)
const isSVGTag = /*#__PURE__*/ makeMap(SVG_TAGS)
const isVoidTag = /*#__PURE__*/ makeMap(VOID_TAGS)

isHTMLTag('html') //=> true
isHTMLTag('HTML') //=> false
isHTMLTag2('HTML') //=> true

// 五星好评
const star = s => {
  let str = '★★★★★☆☆☆☆☆'
  return str.slice(5 - s, 10 - s)
}
